﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Loki.Resources;

namespace Loki.Utils
{
    /// <summary>
    /// Represents a dictionary mapping keys to values.
    /// </summary>
    /// <remarks>
    /// Provides the plumbing for the portions of IDictionary&lt;TKey, TValue&gt; which can reasonably be implemented without any dependency on the underlying representation of the dictionary.
    /// </remarks>
    [DebuggerDisplay(DEBUG_DISPLAY)]
    [DebuggerTypeProxy(PREFIX + DEBUG_PROXY + SUFFIX)]
    public abstract class BaseDictionary<TKey, TValue> : LoggableObject, IDictionary<TKey, TValue>
    {
        private const string DEBUG_DISPLAY = "Count = {Count}";

        private const string DEBUG_PROXY = "DictionaryDebugView`2";

        private const string PREFIX = "System.Collections.Generic.Mscorlib_";

        private const string SUFFIX = ",mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089";

        private KeyCollection _Keys;

        private ValueCollection _Values;

        protected BaseDictionary()
        {
        }

        public abstract int Count { get; }

        public abstract void Clear();

        public abstract void Add(TKey P_Key, TValue P_Value);

        public abstract bool ContainsKey(TKey P_Key);

        public abstract bool Remove(TKey P_Key);

        public abstract bool TryGetValue(TKey P_Key, out TValue P_Value);

        public abstract IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();

        protected abstract void SetValue(TKey P_Key, TValue P_Value);

        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                if (this._Keys == null)
                {
                    Interlocked.CompareExchange(ref _Keys, new KeyCollection(this), null);
                }

                return this._Keys;
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                if (this._Values == null)
                {
                    Interlocked.CompareExchange(ref _Values, new ValueCollection(this), null);
                }

                return this._Values;
            }
        }

        [SuppressMessage("StyleCop.Extensions.LokiNamingRules", "EX1002:ElementMustBeginWithUpperCaseLetter", Justification = "this is indexer keyword")]
        public TValue this[TKey P_Key]
        {
            get
            {
                TValue L_Value;
                if (!this.TryGetValue(P_Key, out L_Value))
                {
                    throw new KeyNotFoundException();
                }

                return L_Value;
            }

            set
            {
                SetValue(P_Key, value);
            }
        }

        public void Add(KeyValuePair<TKey, TValue> P_Item)
        {
            this.Add(P_Item.Key, P_Item.Value);
        }

        public bool Contains(KeyValuePair<TKey, TValue> P_Item)
        {
            TValue L_Value;
            if (!this.TryGetValue(P_Item.Key, out L_Value))
            {
                return false;
            }

            return EqualityComparer<TValue>.Default.Equals(L_Value, P_Item.Value);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] P_Array, int P_ArrayIndex)
        {
            Copy(this, P_Array, P_ArrayIndex);
        }

        public bool Remove(KeyValuePair<TKey, TValue> P_Item)
        {
            if (!this.Contains(P_Item))
            {
                return false;
            }

            return this.Remove(P_Item.Key);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private abstract class Collection<T> : LokiObject, ICollection<T>
        {
            protected readonly IDictionary<TKey, TValue> DICTIONARY;

            protected Collection(IDictionary<TKey, TValue> P_Dictionary)
            {
                this.DICTIONARY = P_Dictionary;
            }

            public int Count
            {
                get { return this.DICTIONARY.Count; }
            }

            public bool IsReadOnly
            {
                get { return true; }
            }

            public void CopyTo(T[] P_Array, int P_ArrayIndex)
            {
                Copy(this, P_Array, P_ArrayIndex);
            }

            public virtual bool Contains(T P_Item)
            {
                foreach (T element in this)
                {
                    if (EqualityComparer<T>.Default.Equals(element, P_Item))
                    {
                        return true;
                    }
                }

                return false;
            }

            public IEnumerator<T> GetEnumerator()
            {
                foreach (KeyValuePair<TKey, TValue> L_Pair in this.DICTIONARY)
                {
                    yield return GetItem(L_Pair);
                }
            }

            protected abstract T GetItem(KeyValuePair<TKey, TValue> P_Pair);

            public bool Remove(T P_Item)
            {
                throw new NotSupportedException();
            }

            public void Add(T P_Item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }
        }

        private class KeyCollection : Collection<TKey>
        {
            public KeyCollection(IDictionary<TKey, TValue> P_Dictionary)
                : base(P_Dictionary)
            {
            }

            protected override TKey GetItem(KeyValuePair<TKey, TValue> P_Pair)
            {
                return P_Pair.Key;
            }

            public override bool Contains(TKey P_Item)
            {
                return this.DICTIONARY.ContainsKey(P_Item);
            }
        }

        private class ValueCollection : Collection<TValue>
        {
            public ValueCollection(IDictionary<TKey, TValue> P_Dictionary)
                : base(P_Dictionary)
            {
            }

            protected override TValue GetItem(KeyValuePair<TKey, TValue> P_Pair)
            {
                return P_Pair.Value;
            }
        }

        private static void Copy<T>(ICollection<T> P_Source, T[] P_Array, int P_ArrayIndex)
        {
            ILog L_Log = Toolkit.Logger.GetLog(typeof(BaseDictionary<TKey, TValue>).Name);

            if (P_Array == null)
            {
                throw Toolkit.ErrorManager.BuildError<ArgumentException>(ErrorMessages.Utils_BaseDictionary_CopyNullDest, L_Log);
            }

            if (P_ArrayIndex < 0 || P_ArrayIndex > P_Array.Length)
            {
                throw Toolkit.ErrorManager.BuildError<ArgumentOutOfRangeException>(ErrorMessages.Utils_BaseDictionary_CopyIndexOutOfRange, L_Log);
            }

            if ((P_Array.Length - P_ArrayIndex) < P_Source.Count)
            {
                throw Toolkit.ErrorManager.BuildError<ArgumentException>(ErrorMessages.Utils_BaseDictionary_CopySmallDest, L_Log);
            }

            foreach (T item in P_Source)
            {
                P_Array[P_ArrayIndex++] = item;
            }
        }
    }
}